package registration;

import static org.junit.Assert.*;

import java.sql.SQLException;
import java.util.*;

import msg.*;

import org.junit.Test;

import user.*;

import common.*;
import common.dal.Transaction;
import common.exceptions.*;
import common.interfaces.IPersistent;

@SuppressWarnings({"static-method", "javadoc"})
public class RegistrationManagerTest extends DBTest {
	private static RegistrationManager	$;
	
	@Override
	protected IPersistent setPersistent(Transaction t) {
		$ = RegistrationManager.getNewInstance(t);
		return $;
	}
	
	@Override
	protected IPersistent getPersistent() {
		return $;
	}
	
	@Test(expected = EventFullException.class)
	public void testRegisterFull() throws Exception {
		User user = null;
		try {
			EventMessage event = createNewEvent();
			assertTrue($.registerUserForEvent(getUser(), event));
			user = LoginManager.getNewInstance(t).registerUser("a" + Calendar.getInstance().getTimeInMillis()
					+ "_", "whatevers", "pass");
			$.registerUserForEvent(user, event);
		} finally {
			if (user != null) {
				LoginManager.getNewInstance(t).deleteUser(user.getLogin(), "pass");
			}
		}
	}
	
	@Test(expected = EventOverException.class)
	public void testRegisterOver() throws Exception {
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.MILLISECOND, 100);
		EventMessage event = MessageManager.getNewInstance(t)
				.createNewEventMessage(new Location(0, 0), "", getUser(), cal, 1);
		Thread.sleep(150);
		$.registerUserForEvent(getUser(), event);
	}
	
	@Test
	public void testRegister() throws Exception {
		EventMessage event = createNewEvent();
		assertTrue($.registerUserForEvent(getUser(), event));
		assertFalse($.registerUserForEvent(getUser(), event));
	}
	
	@Test(expected = EventOverException.class)
	public void testUnregisterOver() throws Exception {
		Calendar eventTime = Calendar.getInstance();
		eventTime.add(Calendar.MILLISECOND, 200);
		EventMessage event = MessageManager.getNewInstance(t)
				.createNewEventMessage(new Location(0, 0), "", getUser(), eventTime, 1);
		try {
			assertTrue($.registerUserForEvent(getUser(), event));
			// EOE should only be thrown when unregistering
		} catch (EventOverException e) {
			throw new AssertionError(e);
		}
		Thread.sleep(200);
		$.unregisterUserForEvent(getUser(), event);
	}
	
	@Test
	public void testUnregister() throws Exception {
		EventMessage event = createNewEvent();
		assertTrue($.registerUserForEvent(getUser(), event));
		assertTrue($.unregisterUserForEvent(getUser(), event));
	}
	
	@Test(expected = InvalidUserException.class)
	public void testGetAllRegistrationsByUserBadUser() throws Exception {
		$.getAllRegistrationsByUser(getFakeUser(), true);
	}
	
	@Test
	public void testGetAllRegistrationsByUser() throws Exception {
		Set<EventMessage> set = new HashSet<>();
		assertEquals(set, new HashSet<>($.getAllRegistrationsByUser(getUser(), true)));
		EventMessage event = createNewEvent();
		$.registerUserForEvent(getUser(), event);
		set.add(event);
		assertEquals(set, new HashSet<>($.getAllRegistrationsByUser(getUser(), true)));
	}
	
	@Test(expected = InvalidEventException.class)
	public void testAddConsensusBadEvent() throws Exception {
		EventMessage event = createNewEvent();
		MessageManager.getNewInstance(t).delete(event.getId());
		$.addConsensus(event, "consensus");
	}
	
	@Test(expected = EventOverException.class)
	public void testAddConsensusEventOver() throws Exception {
		// create an event that is over
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.MILLISECOND, 200);
		EventMessage event = MessageManager.getNewInstance(t)
				.createNewEventMessage(new Location(0.5, 0.5), "", getUser(), cal, 10);
		Thread.sleep(250);
		$.addConsensus(event, "consensus");
	}
	
	@Test
	public void testAddConsensus() throws Exception {
		EventMessage event = createNewEvent();
		$.addConsensus(event, "consensus");
	}
	
	@Test
	public void testRemoveConsensus() throws Exception {
		EventMessage event = createNewEvent();
		Consensus con = $.addConsensus(event, "consensus");
		assertTrue($.removeConsensus(con.getId()));
		assertFalse($.removeConsensus(con.getId()));
	}
	
	@Test
	public void testGetAllEventConsensuses() throws Exception {
		EventMessage event = createNewEvent();
		EventMessage event2 = createNewEvent();
		Consensus con = $.addConsensus(event, "consensus");
		Consensus con2 = $.addConsensus(event, "consensus2");
		$.addConsensus(event2, "consensus3");
		Set<Consensus> expected = new HashSet<>(Arrays.asList(con, con2));
		assertEquals(expected, new HashSet<>($.getAllEventConsensuses(event)));
	}
	
	private EventMessage createNewEvent() throws InvalidUserException, SQLException {
		return MessageManager.getNewInstance(t)
				.createNewEventMessage(new Location(0, 0), "", getUser(), getTomorrow(), 1);
	}
	
	@Test(expected = InvalidKeyException.class)
	public void testGetConsensusBad() throws Exception {
		$.getConsensus(-1);
	}
	
	@Test
	public void testGetConsensus() throws Exception {
		Consensus con = $.addConsensus(getEvent(), "name");
		assertEquals(con, $.getConsensus(con.getId()));
	}
	
	@Test(expected = InvalidUserException.class)
	public void testisUserRegisteredToEventBadUser() throws Exception {
		$.isUserRegisteredToEvent(getFakeUser(), getEvent());
	}
	
	@Test(expected = InvalidEventException.class)
	public void testIsUserRegisteredToEventBadEvent() throws Exception {
		$.isUserRegisteredToEvent(getUser(), getFakeEvent());
	}
	
	@Test
	public void testIsUserRegisteredToEvent() throws Exception {
		assertFalse($.isUserRegisteredToEvent(getUser(), getEvent()));
		$.registerUserForEvent(getUser(), getEvent());
		assertTrue($.isUserRegisteredToEvent(getUser(), getEvent()));
	}
	
	@Test(expected = InvalidEventException.class)
	public void testGetAllRegistrationsToEventBad() throws Exception {
		$.getAllRegistrationsToEvent(getFakeEvent());
	}
	
	@Test
	public void testGetAllRegistrationsToEvent() throws Exception {
		try {
			assertEquals(Collections.emptySet(), new HashSet<>($.getAllRegistrationsToEvent(getEvent())));
			$.registerUserForEvent(getUser(), getEvent());
			assertEquals(new HashSet<>(Arrays.asList(getUser())), new HashSet<>(
					$.getAllRegistrationsToEvent(getEvent())));
		} finally {
			$.unregisterUserForEvent(getUser(), getEvent());
		}
	}
	
	// all DBVote wrappers are not tested
	
	@Test(expected = InvalidUserException.class)
	public void testRemoveAllFromUserBad() throws Exception {
		$.removeAllFromUser(getFakeUser());
	}
	
	@Test
	public void testRemoveAllFromUser() throws Exception {
		EventMessage event2 = null;
		User user2 = null;
		try {
			final User user2f = LoginManager.getNewInstance(t).registerUser("user2", "name", PASSWORD);
			user2 = user2f;
			final User userToBeDeleted = LoginManager.getNewInstance(t)
					.registerUser("deletedUser", "name", PASSWORD);
			// registrations
			$.registerUserForEvent(userToBeDeleted, getEvent());
			$.registerUserForEvent(user2f, getEvent());
			// consensuses
			final Consensus con = $.addConsensus(getEvent(), "conTest");
			EventMessage eventToBeDeleted = MessageManager.getNewInstance(t).createNewEventMessage(new Location(0,
					3), "content", userToBeDeleted, getTomorrow(), 10);
			$.registerUserForEvent(user2f, eventToBeDeleted);
			// votes
			$.vote(con, userToBeDeleted, false);
			$.vote(con, user2f, true);
			event2 = MessageManager.getNewInstance(t)
					.createNewEventMessage(new Location(0.5, 0.5), "event2", user2f, getTomorrow(), 2);
			final Consensus con3 = $.addConsensus(eventToBeDeleted, "con3");
			final Consensus con2 = $.addConsensus(event2, "con2");
			$.registerUserForEvent(userToBeDeleted, con2.getEvent());
			$.vote(con2, userToBeDeleted, false);
			$.removeAllFromUser(userToBeDeleted);
			assertTrue($.isUserRegisteredToEvent(user2f, getEvent()));
			assertFalse($.isUserRegisteredToEvent(userToBeDeleted, getEvent()));
			assertFalse($.isUserRegisteredToEvent(userToBeDeleted, event2));
			assertFalse($.isUserRegisteredToEvent(user2f, eventToBeDeleted));
			assertEquals(Collections.EMPTY_SET, new HashSet<>($.getAllEventConsensuses(eventToBeDeleted)));
			assertThrows(new IExceptionRunnable() {
				@Override
				public void run() throws Exception {
					$.getVote(con2, userToBeDeleted);
				}
			}, IllegalArgumentException.class);
			assertThrows(new IExceptionRunnable() {
				@Override
				public void run() throws Exception {
					$.getVote(con3, user2f);
				}
			}, InvalidConsensusException.class);
		} finally {
			if (user2 != null) {
				RegistrationManager.getNewInstance(t).removeAllFromUser(user2);
				if (event2 != null) {
					MessageManager.getNewInstance(t).delete(event2.getId());
				}
				LoginManager.getNewInstance(t).deleteUser(user2.getLogin(), PASSWORD);
			}
		}
		
	}
	
	@Test
	public void testVote() throws Exception {
		$.registerUserForEvent(getUser(), getEvent());
		Consensus con = $.addConsensus(getEvent(), "conTest");
		$.vote(con, getUser(), false);
	}
	
	@Test(expected = EventOverException.class)
	public void testVoteEventOver() throws Exception {
		User user = LoginManager.getNewInstance(t)
				.registerUser("a" + new Date().getTime(), "no one cares", PASSWORD);
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.MILLISECOND, 50);
		EventMessage event = MessageManager.getNewInstance(t)
				.createNewEventMessage(new Location(0.5, 1), "event content", user, cal, 1);
		RegistrationManager.getNewInstance(t).registerUserForEvent(user, event);
		Consensus con = RegistrationManager.getNewInstance(t).addConsensus(event, "con");
		Thread.sleep(50);
		$.vote(con, user, false);
	}
	
	@Test(expected = InvalidEventException.class)
	public void testRemoveAllEventDataBadEvent() throws Exception {
		$.removeAllEventData(getFakeEvent());
	}
	
	@Test
	public void testRemoveAllEventData() throws Exception {
		EventMessage event = MessageManager.getNewInstance(t)
				.createNewEventMessage(new Location(0.5, 2), "event content", getUser(), getTomorrow(), 1);
		Consensus con = $.addConsensus(event, "con");
		$.registerUserForEvent(getUser(), event);
		$.vote(con, getUser(), false);
		$.removeAllEventData(event);
	}
	
	@Test
	public void testPastUserRegistrations() throws Exception {
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.MILLISECOND, 100);
		EventMessage event = MessageManager.getNewInstance(t)
				.createNewEventMessage(new Location(0.5, 2), "event content", getUser(), cal, 1);
		$.registerUserForEvent(getUser(), event);
		Thread.sleep(150);
		assertEquals(new HashSet<>(), new HashSet<>($.getAllRegistrationsByUser(getUser(), false)));
		assertEquals(new HashSet<>(), new HashSet<>($.getAllRegistrationsByUser(getUser(), null)));
		assertEquals(new HashSet<>(Arrays.asList(event)), new HashSet<>(
				$.getAllRegistrationsByUser(getUser(), true)));
	}
	
	@Test
	public void testDifferentIdsDifferentSession() throws Exception {
		long id1 = $.addConsensus(getEvent(), "con").getId();
		$.removeConsensus(id1);
		t.commit();
		t = new Transaction();
		$ = RegistrationManager.getNewInstance(t);
		long id2 = $.addConsensus(getEvent(), "con").getId();
		assertEquals(id2, id1 + 1);
		$.removeConsensus(id2);
		t.commit();
		t = new Transaction();
		$ = RegistrationManager.getNewInstance(t);
		long id3 = $.addConsensus(getEvent(), "con").getId();
		assertEquals(id3, id2 + 1);
	}
}
